import { makeObservable, configure, getDependencyTree, Reaction, observable, runInAction } from 'mobx';
import React, { useState, forwardRef, memo } from 'react';
import { unstable_batchedUpdates } from 'react-dom';

if (!useState) {
  throw new Error("mobx-react-lite requires React with Hooks support");
}

if (!makeObservable) {
  throw new Error("mobx-react-lite@3 requires mobx at least version 6 to be available");
}

function defaultNoopBatch(callback) {
  callback();
}
function observerBatching(reactionScheduler) {
  if (!reactionScheduler) {
    reactionScheduler = defaultNoopBatch;

    {
      console.warn("[MobX] Failed to get unstable_batched updates from react-dom / react-native");
    }
  }

  configure({
    reactionScheduler: reactionScheduler
  });
}
var isObserverBatched = function isObserverBatched() {
  {
    console.warn("[MobX] Deprecated");
  }

  return true;
};

var deprecatedMessages = [];
function useDeprecated(msg) {
  if (!deprecatedMessages.includes(msg)) {
    deprecatedMessages.push(msg);
    console.warn(msg);
  }
}

function printDebugValue(v) {
  return getDependencyTree(v);
}

var FinalizationRegistryLocal = typeof FinalizationRegistry === "undefined" ? undefined : FinalizationRegistry;

function createTrackingData(reaction) {
  var trackingData = {
    reaction: reaction,
    mounted: false,
    changedBeforeMount: false,
    cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS
  };
  return trackingData;
}
/**
 * The minimum time before we'll clean up a Reaction created in a render
 * for a component that hasn't managed to run its effects. This needs to
 * be big enough to ensure that a component won't turn up and have its
 * effects run without being re-rendered.
 */

var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000;
/**
 * The frequency with which we'll check for leaked reactions.
 */

var CLEANUP_TIMER_LOOP_MILLIS = 10000;

/**
 * FinalizationRegistry-based uncommitted reaction cleanup
 */

function createReactionCleanupTrackingUsingFinalizationRegister(FinalizationRegistry) {
  var cleanupTokenToReactionTrackingMap = new Map();
  var globalCleanupTokensCounter = 1;
  var registry = new FinalizationRegistry(function cleanupFunction(token) {
    var trackedReaction = cleanupTokenToReactionTrackingMap.get(token);

    if (trackedReaction) {
      trackedReaction.reaction.dispose();
      cleanupTokenToReactionTrackingMap["delete"](token);
    }
  });
  return {
    addReactionToTrack: function addReactionToTrack(reactionTrackingRef, reaction, objectRetainedByReact) {
      var token = globalCleanupTokensCounter++;
      registry.register(objectRetainedByReact, token, reactionTrackingRef);
      reactionTrackingRef.current = createTrackingData(reaction);
      reactionTrackingRef.current.finalizationRegistryCleanupToken = token;
      cleanupTokenToReactionTrackingMap.set(token, reactionTrackingRef.current);
      return reactionTrackingRef.current;
    },
    recordReactionAsCommitted: function recordReactionAsCommitted(reactionRef) {
      registry.unregister(reactionRef);

      if (reactionRef.current && reactionRef.current.finalizationRegistryCleanupToken) {
        cleanupTokenToReactionTrackingMap["delete"](reactionRef.current.finalizationRegistryCleanupToken);
      }
    },
    forceCleanupTimerToRunNowForTests: function forceCleanupTimerToRunNowForTests() {// When FinalizationRegistry in use, this this is no-op
    },
    resetCleanupScheduleForTests: function resetCleanupScheduleForTests() {// When FinalizationRegistry in use, this this is no-op
    }
  };
}

function _unsupportedIterableToArray(o, minLen) {
  if (!o) return;
  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  var n = Object.prototype.toString.call(o).slice(8, -1);
  if (n === "Object" && o.constructor) n = o.constructor.name;
  if (n === "Map" || n === "Set") return Array.from(o);
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}

function _arrayLikeToArray(arr, len) {
  if (len == null || len > arr.length) len = arr.length;

  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

  return arr2;
}

function _createForOfIteratorHelperLoose(o, allowArrayLike) {
  var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
  if (it) return (it = it.call(o)).next.bind(it);

  if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
    if (it) o = it;
    var i = 0;
    return function () {
      if (i >= o.length) return {
        done: true
      };
      return {
        done: false,
        value: o[i++]
      };
    };
  }

  throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}

/**
 * timers, gc-style, uncommitted reaction cleanup
 */

function createTimerBasedReactionCleanupTracking() {
  /**
   * Reactions created by components that have yet to be fully mounted.
   */
  var uncommittedReactionRefs = new Set();
  /**
   * Latest 'uncommitted reactions' cleanup timer handle.
   */

  var reactionCleanupHandle;
  /* istanbul ignore next */

  /**
   * Only to be used by test functions; do not export outside of mobx-react-lite
   */

  function forceCleanupTimerToRunNowForTests() {
    // This allows us to control the execution of the cleanup timer
    // to force it to run at awkward times in unit tests.
    if (reactionCleanupHandle) {
      clearTimeout(reactionCleanupHandle);
      cleanUncommittedReactions();
    }
  }
  /* istanbul ignore next */


  function resetCleanupScheduleForTests() {
    if (uncommittedReactionRefs.size > 0) {
      for (var _iterator = _createForOfIteratorHelperLoose(uncommittedReactionRefs), _step; !(_step = _iterator()).done;) {
        var ref = _step.value;
        var tracking = ref.current;

        if (tracking) {
          tracking.reaction.dispose();
          ref.current = null;
        }
      }

      uncommittedReactionRefs.clear();
    }

    if (reactionCleanupHandle) {
      clearTimeout(reactionCleanupHandle);
      reactionCleanupHandle = undefined;
    }
  }

  function ensureCleanupTimerRunning() {
    if (reactionCleanupHandle === undefined) {
      reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS);
    }
  }

  function scheduleCleanupOfReactionIfLeaked(ref) {
    uncommittedReactionRefs.add(ref);
    ensureCleanupTimerRunning();
  }

  function recordReactionAsCommitted(reactionRef) {
    uncommittedReactionRefs["delete"](reactionRef);
  }
  /**
   * Run by the cleanup timer to dispose any outstanding reactions
   */


  function cleanUncommittedReactions() {
    reactionCleanupHandle = undefined; // Loop through all the candidate leaked reactions; those older
    // than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied.

    var now = Date.now();
    uncommittedReactionRefs.forEach(function (ref) {
      var tracking = ref.current;

      if (tracking) {
        if (now >= tracking.cleanAt) {
          // It's time to tidy up this leaked reaction.
          tracking.reaction.dispose();
          ref.current = null;
          uncommittedReactionRefs["delete"](ref);
        }
      }
    });

    if (uncommittedReactionRefs.size > 0) {
      // We've just finished a round of cleanups but there are still
      // some leak candidates outstanding.
      ensureCleanupTimerRunning();
    }
  }

  return {
    addReactionToTrack: function addReactionToTrack(reactionTrackingRef, reaction,
    /**
     * On timer based implementation we don't really need this object,
     * but we keep the same api
     */
    objectRetainedByReact) {
      reactionTrackingRef.current = createTrackingData(reaction);
      scheduleCleanupOfReactionIfLeaked(reactionTrackingRef);
      return reactionTrackingRef.current;
    },
    recordReactionAsCommitted: recordReactionAsCommitted,
    forceCleanupTimerToRunNowForTests: forceCleanupTimerToRunNowForTests,
    resetCleanupScheduleForTests: resetCleanupScheduleForTests
  };
}

var _ref = FinalizationRegistryLocal ? /*#__PURE__*/createReactionCleanupTrackingUsingFinalizationRegister(FinalizationRegistryLocal) : /*#__PURE__*/createTimerBasedReactionCleanupTracking(),
    addReactionToTrack = _ref.addReactionToTrack,
    recordReactionAsCommitted = _ref.recordReactionAsCommitted,
    resetCleanupScheduleForTests = _ref.resetCleanupScheduleForTests;

var globalIsUsingStaticRendering = false;
function enableStaticRendering(enable) {
  globalIsUsingStaticRendering = enable;
}
function isUsingStaticRendering() {
  return globalIsUsingStaticRendering;
}

function observerComponentNameFor(baseComponentName) {
  return "observer" + baseComponentName;
}
/**
 * We use class to make it easier to detect in heap snapshots by name
 */


var ObjectToBeRetainedByReact = function ObjectToBeRetainedByReact() {};

function objectToBeRetainedByReactFactory() {
  return new ObjectToBeRetainedByReact();
}

function useObserver(fn, baseComponentName) {
  if (baseComponentName === void 0) {
    baseComponentName = "observed";
  }

  if (isUsingStaticRendering()) {
    return fn();
  }

  var _React$useState = React.useState(objectToBeRetainedByReactFactory),
      objectRetainedByReact = _React$useState[0]; // Force update, see #2982


  var _React$useState2 = React.useState(),
      setState = _React$useState2[1];

  var forceUpdate = function forceUpdate() {
    return setState([]);
  }; // StrictMode/ConcurrentMode/Suspense may mean that our component is
  // rendered and abandoned multiple times, so we need to track leaked
  // Reactions.


  var reactionTrackingRef = React.useRef(null);

  if (!reactionTrackingRef.current) {
    // First render for this component (or first time since a previous
    // reaction from an abandoned render was disposed).
    var newReaction = new Reaction(observerComponentNameFor(baseComponentName), function () {
      // Observable has changed, meaning we want to re-render
      // BUT if we're a component that hasn't yet got to the useEffect()
      // stage, we might be a component that _started_ to render, but
      // got dropped, and we don't want to make state changes then.
      // (It triggers warnings in StrictMode, for a start.)
      if (trackingData.mounted) {
        // We have reached useEffect(), so we're mounted, and can trigger an update
        forceUpdate();
      } else {
        // We haven't yet reached useEffect(), so we'll need to trigger a re-render
        // when (and if) useEffect() arrives.
        trackingData.changedBeforeMount = true;
      }
    });
    var trackingData = addReactionToTrack(reactionTrackingRef, newReaction, objectRetainedByReact);
  }

  var reaction = reactionTrackingRef.current.reaction;
  React.useDebugValue(reaction, printDebugValue);
  React.useEffect(function () {
    // Called on first mount only
    recordReactionAsCommitted(reactionTrackingRef);

    if (reactionTrackingRef.current) {
      // Great. We've already got our reaction from our render;
      // all we need to do is to record that it's now mounted,
      // to allow future observable changes to trigger re-renders
      reactionTrackingRef.current.mounted = true; // Got a change before first mount, force an update

      if (reactionTrackingRef.current.changedBeforeMount) {
        reactionTrackingRef.current.changedBeforeMount = false;
        forceUpdate();
      }
    } else {
      // The reaction we set up in our render has been disposed.
      // This can be due to bad timings of renderings, e.g. our
      // component was paused for a _very_ long time, and our
      // reaction got cleaned up
      // Re-create the reaction
      reactionTrackingRef.current = {
        reaction: new Reaction(observerComponentNameFor(baseComponentName), function () {
          // We've definitely already been mounted at this point
          forceUpdate();
        }),
        mounted: true,
        changedBeforeMount: false,
        cleanAt: Infinity
      };
      forceUpdate();
    }

    return function () {
      reactionTrackingRef.current.reaction.dispose();
      reactionTrackingRef.current = null;
    };
  }, []); // render the original component, but have the
  // reaction track the observables, so that rendering
  // can be invalidated (see above) once a dependency changes

  var rendering;
  var exception;
  reaction.track(function () {
    try {
      rendering = fn();
    } catch (e) {
      exception = e;
    }
  });

  if (exception) {
    throw exception; // re-throw any exceptions caught during rendering
  }

  return rendering;
}

var warnObserverOptionsDeprecated = true;
var hasSymbol = typeof Symbol === "function" && Symbol["for"]; // Using react-is had some issues (and operates on elements, not on types), see #608 / #609

var ReactForwardRefSymbol = hasSymbol ? /*#__PURE__*/Symbol["for"]("react.forward_ref") : typeof forwardRef === "function" && /*#__PURE__*/forwardRef(function (props) {
  return null;
})["$$typeof"];
var ReactMemoSymbol = hasSymbol ? /*#__PURE__*/Symbol["for"]("react.memo") : typeof memo === "function" && /*#__PURE__*/memo(function (props) {
  return null;
})["$$typeof"]; // n.b. base case is not used for actual typings or exported in the typing files

function observer(baseComponent, // TODO remove in next major
options) {
  var _options$forwardRef;

  if ( warnObserverOptionsDeprecated && options) {
    warnObserverOptionsDeprecated = false;
    console.warn("[mobx-react-lite] `observer(fn, { forwardRef: true })` is deprecated, use `observer(React.forwardRef(fn))`");
  }

  if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) {
    throw new Error("[mobx-react-lite] You are trying to use `observer` on a function component wrapped in either another `observer` or `React.memo`. The observer already applies 'React.memo' for you.");
  } // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307


  if (isUsingStaticRendering()) {
    return baseComponent;
  }

  var useForwardRef = (_options$forwardRef = options == null ? void 0 : options.forwardRef) != null ? _options$forwardRef : false;
  var render = baseComponent;
  var baseComponentName = baseComponent.displayName || baseComponent.name; // If already wrapped with forwardRef, unwrap,
  // so we can patch render and apply memo

  if (ReactForwardRefSymbol && baseComponent["$$typeof"] === ReactForwardRefSymbol) {
    useForwardRef = true;
    render = baseComponent["render"];

    if (typeof render !== "function") {
      throw new Error("[mobx-react-lite] `render` property of ForwardRef was not a function");
    }
  }

  var observerComponent = function observerComponent(props, ref) {
    return useObserver(function () {
      return render(props, ref);
    }, baseComponentName);
  }; // Don't set `displayName` for anonymous components,
  // so the `displayName` can be customized by user, see #3192.


  if (baseComponentName !== "") {
    observerComponent.displayName = baseComponentName;
  } // Support legacy context: `contextTypes` must be applied before `memo`


  if (baseComponent.contextTypes) {
    observerComponent.contextTypes = baseComponent.contextTypes;
  }

  if (useForwardRef) {
    // `forwardRef` must be applied prior `memo`
    // `forwardRef(observer(cmp))` throws:
    // "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))"
    observerComponent = forwardRef(observerComponent);
  } // memo; we are not interested in deep updates
  // in props; we assume that if deep objects are changed,
  // this is in observables, which would have been tracked anyway


  observerComponent = memo(observerComponent);
  copyStaticProperties(baseComponent, observerComponent);

  {
    Object.defineProperty(observerComponent, "contextTypes", {
      set: function set() {
        var _this$type;

        throw new Error("[mobx-react-lite] `" + (this.displayName || ((_this$type = this.type) == null ? void 0 : _this$type.displayName) || "Component") + ".contextTypes` must be set before applying `observer`.");
      }
    });
  }

  return observerComponent;
} // based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js

var hoistBlackList = {
  $$typeof: true,
  render: true,
  compare: true,
  type: true,
  // Don't redefine `displayName`,
  // it's defined as getter-setter pair on `memo` (see #3192).
  displayName: true
};

function copyStaticProperties(base, target) {
  Object.keys(base).forEach(function (key) {
    if (!hoistBlackList[key]) {
      Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key));
    }
  });
}

function ObserverComponent(_ref) {
  var children = _ref.children,
      render = _ref.render;
  var component = children || render;

  if (typeof component !== "function") {
    return null;
  }

  return useObserver(component);
}

{
  ObserverComponent.propTypes = {
    children: ObserverPropsCheck,
    render: ObserverPropsCheck
  };
}

ObserverComponent.displayName = "Observer";

function ObserverPropsCheck(props, key, componentName, location, propFullName) {
  var extraKey = key === "children" ? "render" : "children";
  var hasProp = typeof props[key] === "function";
  var hasExtraProp = typeof props[extraKey] === "function";

  if (hasProp && hasExtraProp) {
    return new Error("MobX Observer: Do not use children and render in the same time in`" + componentName);
  }

  if (hasProp || hasExtraProp) {
    return null;
  }

  return new Error("Invalid prop `" + propFullName + "` of type `" + typeof props[key] + "` supplied to" + " `" + componentName + "`, expected `function`.");
}

function useLocalObservable(initializer, annotations) {
  return useState(function () {
    return observable(initializer(), annotations, {
      autoBind: true
    });
  })[0];
}

function useAsObservableSource(current) {
  useDeprecated("[mobx-react-lite] 'useAsObservableSource' is deprecated, please store the values directly in an observable, for example by using 'useLocalObservable', and sync future updates using 'useEffect' when needed. See the README for examples.");

  var _useState = useState(function () {
    return observable(current, {}, {
      deep: false
    });
  }),
      res = _useState[0];

  runInAction(function () {
    Object.assign(res, current);
  });
  return res;
}

function useLocalStore(initializer, current) {
  useDeprecated("[mobx-react-lite] 'useLocalStore' is deprecated, use 'useLocalObservable' instead.");
  var source = current && useAsObservableSource(current);
  return useState(function () {
    return observable(initializer(source), undefined, {
      autoBind: true
    });
  })[0];
}

observerBatching(unstable_batchedUpdates);
function useObserver$1(fn, baseComponentName) {
  if (baseComponentName === void 0) {
    baseComponentName = "observed";
  }

  {
    useDeprecated("[mobx-react-lite] 'useObserver(fn)' is deprecated. Use `<Observer>{fn}</Observer>` instead, or wrap the entire component in `observer`.");
  }

  return useObserver(fn, baseComponentName);
}
function useStaticRendering(enable) {
  {
    console.warn("[mobx-react-lite] 'useStaticRendering' is deprecated, use 'enableStaticRendering' instead");
  }

  enableStaticRendering(enable);
}

export { ObserverComponent as Observer, resetCleanupScheduleForTests as clearTimers, enableStaticRendering, isObserverBatched, isUsingStaticRendering, observer, observerBatching, useAsObservableSource, useLocalObservable, useLocalStore, useObserver$1 as useObserver, useStaticRendering };
//# sourceMappingURL=mobxreactlite.esm.development.js.map
